using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MKDS_Course_Editor.GCN.BMD
{
    class TEX
    {
        //header format for 'bmd3' files, seems to be slightly different for 'jpa1'
        public class Tex1Header
        {
            public string tag; //'TEX1'
            public UInt32 sizeOfSection;
            public UInt16 numImages;
            public UInt16 unknown; //padding, usually 0xffff
            public UInt32 textureHeaderOffset; //numImages bti image headers are stored here (see bti spec)
            //note: several image headers may point to same image data
            //offset relative to Tex1Header start

            public UInt32 stringTableOffset;   //stores one filename for each image (TODO: details on stringtables)
            //offset relative to Tex1Header start  
        }


        const int I4 = 0;
        const int I8 = 1;
        const int A4_I4 = 2;
        const int A8_I8 = 3;
        const int R5_G6_B5 = 4;
        const int A3_RGB5 = 5;
        const int ARGB8 = 6;
        const int INDEX4 = 8;
        const int INDEX8 = 9;
        const int INDEX14_X2 = 10;
        const int S3TC1 = 14;

        const int PAL_A8_I8 = 0;
        const int PAL_R5_G6_B5 = 1;
        const int PAL_A3_RGB5 = 2;

        public class TextureHeader
        {
            //0 - i4
            //1 - i8
            //2 - a4i4
            //3 - a8i8
            //4 - r5g6b5
            //5 - rgb5a3
            //6 - argb8
            //
            //8 - index4
            //9 - index8
            //10 -index14x2
            //
            //14 - dxt1 compressed
            //(see tpl's format in yagcd for more details)
            public byte format;
            public byte unknown; //0 or cc, 1, 2 (geostar (texmatrix).bmd)
            public UInt16 width;
            public UInt16 height;

            /*
              from gx.h:
              0: clamp to edge
              1: repeat
              2: mirror
            */
            public byte wrapS;
            public byte wrapT;

            public byte unknown3; // 0, 1 (gnd)


            //0 - a8i8
            //1 - r5g6b5
            //2 - rgb5a3
            //(see tpl's palette format in yagcd for more details)
            public byte paletteFormat;
            public UInt16 paletteNumEntries;
            public UInt32 paletteOffset; //palette data


            public UInt32 unknown5; //sometimes 0x1_00_0000 when mipmapCount > 1

            //0 - nearest
            //1 - linear
            //2 - near_mip_near
            //3 - lin_mip_near
            //4 - near_mip_lin
            //5 - lin_mip_lin
            public byte minFilter;
            public byte magFilter; //??

            public UInt16 unknown7; //0 most of the time,
            //sometimes 0x10, 0x18 (mariocap), 0x20, 0x28
            public byte mipmapCount;
            public byte unknown8; //0 (nomips), 1 (nomips), d, 48, 4d, 56 (nomips),
            //58, 61, 8f, da or ff (hmm...0-ff ;-) )
            public UInt16 unknown9; //0 (nomips), 7, 20, 74 (in airport.bmd), ffee, ffe3 (sea.bmd)

            public UInt32 dataOffset; //image data

            //some of the unknown data could be render state?
            //(lod bias, transparent color (? could be in shader as well...), ...)
            /*
            void GX_InitTexObj(GXTexObj *obj,void *img_ptr,UInt16 wd,UInt16 ht,byte fmt,byte wrap_s,byte wrap_t,byte mipmap);
            void GX_InitTexObjCI(GXTexObj *obj,void *img_ptr,UInt16 wd,UInt16 ht,byte fmt,byte wrap_s,byte wrap_t,byte mipmap,UInt32 tlut_name);
            void GX_InitTexObjLOD(GXTexObj *obj,byte minfilt,byte magfilt,f32 minlod,f32 maxlod,f32 lodbias,byte biasclamp,byte edgelod,byte maxaniso);
            void GX_SetTexCoorScaleManually(byte texcoord,byte enable,UInt16 ss,UInt16 ts);
            void GX_SetTexCoordBias(byte texcoord,byte s_enable,byte t_enable);
            */
        }
        public class ImageHeader
        {
            public Image data;
            public string name;

            /*
              from gx.h:
              0: clamp to edge
              1: repeat
              2: mirror
            */
            public byte wrapS, wrapT;

            public byte minFilter, magFilter;

            //TODO: unknown fields
        }

        const int I8_A8 = 3;
        const int RGBA8 = 6;
        const int DXT1 = 14;

        public class Image
        {
            public int format;
            public int width, height;

            public List<byte> mipmaps = new List<byte>(); //points into imageData
            public List<int> sizes = new List<int>(); //image data size for each mipmap
            public List<byte> imageData = new List<byte>();

            //NOTE: palettized images are converted
            //to non-palettized images during load time,
            //i4 is converted to i8, a4i4 and a8i8 is converted to i8a8.
            //r5g5b5a3 and r5g6b5 are converted to rgba8.
            //(that is, only formats 1 (i8), 3* (i8a8), 6 (rgba8)
            //and 14 (dxt1) are used after conversion)

            //TODO: gl image conversions (rgba -> abgr, ai -> ia
            //somewhere else?)

            //TODO: this is temporary and belongs somewhere else:
            public uint texId;

            public int originalFormat, paletteFormat;
        }

        public class Tex1
        {
            public List<ImageHeader> imageHeaders = new List<ImageHeader>();

            //because several image headers might point to the
            //same image data, this data is stored
            //separately to save some memory
            //(this way only about 1/6 of the memory required
            //otherwise is used)
            public List<Image> images = new List<Image>();
        }

        //checks if a positive number is a power of two
        private static bool isPot(int i)
        {
            if (i == 0)
                return false;

            return (i & (i - 1)) == 0;
        }

        private static int nextPot(int i)
        {
            int ret = 1;
            while (ret < i)
                ret *= 2;
            return ret;
        }
        private static void readTex1Header(EndianBinaryReader er, Tex1Header h)
        {
            h.tag = er.ReadString(Encoding.ASCII, 4);
            h.sizeOfSection = er.ReadUInt32();
            h.numImages = er.ReadUInt16();
            h.unknown = er.ReadUInt16();
            h.textureHeaderOffset = er.ReadUInt32();
            h.stringTableOffset = er.ReadUInt32();
        }

        private static void readTextureHeader(EndianBinaryReader er, TextureHeader texHeader)
        {
            texHeader.format = er.ReadByte();
            texHeader.unknown = er.ReadByte();
            texHeader.width = er.ReadUInt16();
            texHeader.height = er.ReadUInt16();
            texHeader.wrapS = er.ReadByte();
            texHeader.wrapT = er.ReadByte();
            texHeader.unknown3 = er.ReadByte();
            texHeader.paletteFormat = er.ReadByte();
            texHeader.paletteNumEntries = er.ReadUInt16();
            texHeader.paletteOffset = er.ReadUInt32();
            texHeader.unknown5 = er.ReadUInt32();
            texHeader.minFilter = er.ReadByte();
            texHeader.magFilter = er.ReadByte();
            texHeader.unknown7 = er.ReadUInt16();
            texHeader.mipmapCount = er.ReadByte();
            texHeader.unknown8 = er.ReadByte();
            texHeader.unknown9 = er.ReadUInt16();
            texHeader.dataOffset = er.ReadUInt32();
        }

        public static void dumpTex1(EndianBinaryReader er, Tex1 dst)
        {
            int tex1Offset = (int)er.BaseStream.Position;

            //read textureblock header
            Tex1Header h = new Tex1Header();
            readTex1Header(er, h);

            //read stringtable
            List<string> stringtable = new List<string>();
            Helper.readStringtable(tex1Offset + (int)h.stringTableOffset, er, stringtable);

            if (stringtable.Count != h.numImages)
            {
                //warn("tex1: number of strings (%d) doesn't match number of images (%d)",
                //  stringtable.size(), h.numImages);
            }

            //read all image headers before loading the actual image
            //data, because several headers can refer to the same data
            er.BaseStream.Position = tex1Offset + h.textureHeaderOffset;
            int i;
            List<TextureHeader> texHeaders = new List<TextureHeader>();
            texHeaders.AddRange(new TextureHeader[h.numImages]);
            Dictionary<long, TextureHeader> imageOffsets = new Dictionary<long, TextureHeader>(); //detects multiple offsets
            for (i = 0; i < h.numImages; ++i)
            {
                texHeaders[i] = new TextureHeader();
                readTextureHeader(er, texHeaders[i]);

                //check if this header refers to existing data
                int effectiveOffset = (int)texHeaders[i].dataOffset + 0x20 * i;
                if (imageOffsets.ContainsKey(effectiveOffset) == false)
                    imageOffsets.Add(effectiveOffset, texHeaders[i]);
                else
                {
                    //debug info
                    TextureHeader a = texHeaders[i], b = imageOffsets[effectiveOffset];
                    if (a.width != b.width || a.height != b.height || a.format != b.format
                      || a.mipmapCount != b.mipmapCount
                        //|| a.wrapS != b.wrapS || a.wrapT != b.wrapT //this is a per-header value (see ji.bdl in zelda)
                      )
                    {
                        //warn("tex1: two headers refering to same data have different formats");
                        //TODO: what if effective palette offsets differ?
                    }
                }
            }

            //read image data
            dst.imageHeaders.AddRange(new ImageHeader[h.numImages]);
            dst.images.AddRange(new Image[imageOffsets.Count]);
            Dictionary<long, Image> loadedImages = new Dictionary<long, Image>();
            int j = 0;
            for (i = 0; i < h.numImages; ++i)
            {
                dst.imageHeaders[i].wrapS = texHeaders[i].wrapS;
                dst.imageHeaders[i].wrapT = texHeaders[i].wrapT;
                dst.imageHeaders[i].minFilter = texHeaders[i].minFilter;
                dst.imageHeaders[i].magFilter = texHeaders[i].magFilter;

                if (i < stringtable.Count) //should always be true
                    dst.imageHeaders[i].name = stringtable[i];

                //check if the image needed by current header is
                //already loaded, if not, load it
                int effectiveOffset = (int)texHeaders[i].dataOffset + 0x20 * i;
                if (loadedImages.ContainsKey(effectiveOffset))
                    dst.imageHeaders[i].data = loadedImages[effectiveOffset];
                else
                {
                    Image curr = dst.images[j];
                    ++j;

                    dst.imageHeaders[i].data = curr;
                    loadedImages[effectiveOffset] = curr;

                    loadAndConvertImage(er, texHeaders[i],
                                        tex1Offset + h.textureHeaderOffset + 0x20 * i, curr);
                }
            }

        }
        private static void loadAndConvertImage(EndianBinaryReader er, TextureHeader h, long baseOffset,
                         Image curr)
        {
            int i;

            curr.width = h.width;
            curr.height = h.height;
            curr.format = h.format;

            if ((h.format == 8 || h.format == 9 || h.format == 10)
              && (h.paletteFormat != 1 && h.paletteFormat != 2))
            {                                                     //never tested such an image,
                //but yagcd says theres also a palette format 0
                //warn("found format %d, palette format %d", h.format, h.paletteFormat);
            }


            List<byte> palette = new List<byte>();
            if (h.paletteNumEntries != 0)
            {
                //read palette
                er.BaseStream.Position = baseOffset + h.paletteOffset;
                palette.AddRange(er.ReadBytes(h.paletteNumEntries * 2));
            }

            //calculate required image size
            int totalRequiredSize = 0;
            int wid = h.width, hyt = h.height;
            for (i = 0; i < h.mipmapCount; ++i)
            {
                totalRequiredSize += getUncompressedBufferSize(h.format, wid, hyt, h.paletteFormat);
                wid /= 2; hyt /= 2;
            }

            //get memory for image, set mipmap pointers and load image

            if (h.dataOffset == 0) //TODO: twilight princess does that
            {
                //warn("What to do, what to do? (data offset in image is 0)\n");
            }

            er.BaseStream.Position = baseOffset + h.dataOffset;
            curr.imageData.AddRange(new byte[totalRequiredSize]);
            totalRequiredSize = 0;
            wid = h.width; hyt = h.height;
            curr.mipmaps.AddRange(new byte[h.mipmapCount]);
            curr.sizes.AddRange(new int[h.mipmapCount]);
            for (i = 0; i < h.mipmapCount; ++i)
            {
                curr.mipmaps[i] = curr.imageData[totalRequiredSize];

                curr.sizes[i] =
                  getUncompressedBufferSize(h.format, wid, hyt, h.paletteFormat);

                //read image
                if (h.dataOffset != 0)
                    curr.format = readImage(er, wid, hyt, h.format,
                      palette[0], h.paletteFormat, curr.mipmaps[i]);
                else
                {
                    //this texture is probably rendered at runtime. for now, fill it with
                    //white
                    curr.format = getUncompressedBufferFormat(h.format, h.paletteFormat);
                    if (curr.format != DXT1)
                    {
                        for (int j = i; j < curr.sizes[i] + i; j++)
                        {
                            curr.mipmaps[j] = 0xff;
                        }
                    }
                    else
                    {
                        byte[] whiteBlock = { 0xff, 0xff, 0x00, 0x00,
                                  0x00, 0x00, 0x00, 0x00 };
                        for (int c = 0; c < curr.sizes[i] / 8; ++c)
                        {
                            Array.Copy(whiteBlock, 0, curr.mipmaps.ToArray(), i + 8 * c, 8);
                        }
                    }
                }

                totalRequiredSize += getUncompressedBufferSize(h.format, wid, hyt, h.paletteFormat);
                wid /= 2; hyt /= 2;
            }

            curr.originalFormat = h.format;
            curr.paletteFormat = h.paletteFormat;
        }
        //returns new format
        byte readImage(EndianBinaryReader er, int w, int h, byte format, byte[] palette, byte paletteFormat, byte[] dest)
{
  int srcBufferSize = getCompressedBufferSize(format, w, h);
  List<byte> srcVec = new List<byte>();
            srcVec.AddRange(new byte[srcBufferSize]);
  byte[] src = srcVec.ToArray();
            src = er.ReadBytes(srcBufferSize);

  //do format conversions, unpack blocks
  switch(format)
  {
    case I4: //i4 -> i8
      fix8x8Expand(dest, src, w, h);
      return I8;

    case I8: //i8
      fix8x4(dest, src, w, h);
      return I8;

    case A4_I4: //i4a4 -> i8a8
      fix8x4Expand(dest, src, w, h);
      return I8_A8;

    case A8_I8: //i8a8
      fix4x4(dest, src, w, h);
      return I8_A8;

    case R5_G6_B5: //r5g6b5 -> rgba8
      fixR5G6B5(dest, src, w, h);
      return RGBA8;

    case A3_RGB5: //rgb5a3 -> rgba8
      fixRgb5A3(dest, src, w, h);
      return RGBA8;

    case ARGB8: //argb8 -> rgba8
      fixRGBA8(dest, src, w, h);
      return RGBA8;


    case INDEX4:
    case INDEX8:
    case INDEX14_X2:
    {
      //needed for palette conversions
      //(*2 for expaned i4->i8 case)
        List<byte> tmpVec = new List<byte>();
        tmpVec.AddRange(new byte[2 * srcBufferSize]);
        byte[] tmp = tmpVec.ToArray();
      switch(format)
      {
        case INDEX4:
          fix8x8NoExpand(tmp, src, w, h);
          unpack8(dest, tmp, w, h, palette, paletteFormat);
          break;

        case INDEX8:
          fix8x4(tmp, src, w, h);
          unpack8(dest, tmp, w, h, palette, paletteFormat);
          break;

        case INDEX14_X2:
          fix4x4(tmp, src, w, h);
          unpack16(dest, tmp, w, h, palette, paletteFormat);
          break;
      }

      switch(paletteFormat)
      {
        case PAL_A8_I8:
          return I8_A8;
        case PAL_R5_G6_B5:
        case PAL_A3_RGB5:
          return RGBA8;
        default:
          //warn("tex1: unsupported palette format %d", paletteFormat);
          return 0xff; //TODO: ?
      }
    }


    case S3TC1:
      fixS3TC1(dest, src, w, h);
      return DXT1;

    default:
      //warn("unsupported image format %d", format);
      return 0xff; //TODO: ?
  }
}


//new, fixed version
void fix8x8Expand(byte[] dest, byte[] src, int w, int h)
{
  //convert to i8 during block swapping
  int si = 0;
  for(int y = 0; y < h; y += 8)
    for(int x = 0; x < w; x += 8)
      for(int dy = 0; dy < 8; ++dy)
        for(int dx = 0; dx < 8; dx += 2, ++si)
          if(x + dx < w && y + dy < h)
          {
            //http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
            byte t = (byte)(src[si] & 0xf0);
            dest[w*(y + dy) + x + dx] =(byte)(t | (t >> 4));
            t = (byte)(src[si] & 0xf);
            dest[w*(y + dy) + x + dx + 1] = (byte)((t << 4) | t);
          }
}


void fix8x8NoExpand(byte[] dest, byte[] src, int w, int h)
{
  //convert to i8 during block swapping
  int si = 0;
  for(int y = 0; y < h; y += 8)
    for(int x = 0; x < w; x += 8)
      for(int dy = 0; dy < 8; ++dy)
        for(int dx = 0; dx < 8; dx += 2, ++si)
          if(x + dx < w && y + dy < h)
          {
            //http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
            byte t = (byte)(src[si] & 0xf0);
            dest[w*(y + dy) + x + dx] = (byte)(t >> 4);
            t = (byte)(src[si] & 0xf);
            dest[w*(y + dy) + x + dx + 1] = t;
          }
}


void fix8x4(byte[] dest, byte[] src, int w, int h)
{
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 8)
      for(int dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 8; ++dx, ++si)
          if(x + dx < w && y + dy < h)
            dest[w*(y + dy) + x + dx] = src[si];
}

void fix8x4Expand(byte[] dest, byte[] src, int w, int h)
{
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 8)
      for(int dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 8; ++dx, ++si)
          if(x + dx < w && y + dy < h)
          {
            byte lum = (byte)(src[si] & 0xf);
            lum |= (byte)(lum << 4);
            byte alpha = (byte)(src[si] & 0xf0);
            alpha |= (byte)(alpha >> 4);
            dest[2*(w*(y + dy) + x + dx)] = lum;
            dest[2*(w*(y + dy) + x + dx) + 1] = alpha;
          }
}

void fix4x4(byte[] dest, byte[] src, int w, int h)
{
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 4)
      for(int dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 4; ++dx, si += 2)
          if(x + dx < w && y + dy < h)
          {
            //without byte swapping the result looks wrong. do tex1 blocks
            //store ai8 instead of ia8?
            int di = 2*(w*(y + dy) + x + dx);
            dest[di + 0] = src[si + 1];
            dest[di + 1] = src[si + 0];
          }
}

void r5g6b5ToRgba8(UInt16 srcPixel, byte[] dest)
{
  byte r, g, b;
  r = (byte)((srcPixel & 0xf100) >> 11);
  g = (byte)((srcPixel & 0x7e0) >> 5);
  b = (byte)(srcPixel & 0x1f);

  //http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
  r = (byte)((r << (8 - 5)) | (r >> (10 - 8)));
  g = (byte)((g << (8 - 6)) | (g >> (12 - 8)));
  b = (byte)((b << (8 - 5)) | (b >> (10 - 8)));

  dest[0] = r;
  dest[1] = g;
  dest[2] = b;
  dest[3] = 0xff;
}

void fixR5G6B5(byte[] dest, byte[] src, int w, int h)
{
  //convert to rgba8 during block swapping
  //4x4 tiles
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 4)
      for(int dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 4; ++dx, si += 2)
          if(x + dx < w && y + dy < h)
          {
            UInt16 srcPixel = Helper.memWORD(src + si);
            r5g6b5ToRgba8(srcPixel, dest[4*(w*(y + dy) + x + dx)]);
          }
}

void fixRGBA8(byte[] dest, byte[] src, int w, int h)
{
  //2 4x4 input tiles per 4x4 output tile, first stores AR, second GB
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 4)
    {
      int dy;

      //to have the texture in the format wanted by opengl,
      //we have to convert from argb to rgba

      //this is AR
      for(dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 4; ++dx, si += 2)
          if(x + dx < w && y + dy < h)
          {
            //convert ar to rXXa
            Int32 di = 4*(w*(y + dy) + x + dx);
            dest[di + 0] = src[si + 1];
            dest[di + 3] = src[si + 0];
          }

      //this is GB
      for(dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 4; ++dx, si += 2)
          if(x + dx < w && y + dy < h)
          {
            //convert gb to XgbX and or with previous value
            Int32 di = 4*(w*(y + dy) + x + dx);
            dest[di + 1] = src[si + 0];
            dest[di + 2] = src[si + 1];
          }
    }
}

void rgb5a3ToRgba8(ushort srcPixel, byte[] dest)
{
  byte r, g, b, a;

  //http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
  if((srcPixel & 0x8000) == 0x8000) //rgb5
  {
    a = 0xff;

    r = (byte)((srcPixel & 0x7c00) >> 10);
    r = (byte)((r << (8-5)) | (r >> (10-8)));

    g = (byte)((srcPixel & 0x3e0) >> 5);
    g = (byte)((g << (8-5)) | (g >> (10-8)));

    b = (byte)(srcPixel & 0x1f);
    b = (byte)((b << (8-5)) | (b >> (10-8)));
  }
  else //a3rgb4
  {
    a = (byte)((srcPixel & 0x7000) >> 12);
    a = (byte)((a << (8-3)) | (a << (8-6)) | (a >> (9-8)));

    r = (byte)((srcPixel & 0xf00) >> 8);
    r = (byte)((r << (8-4)) | r);

    g = (byte)((srcPixel & 0xf0) >> 4);
    g = (byte)((g << (8-4)) | g);

    b = (byte)(srcPixel & 0xf);
    b = (byte)((b << (8-4)) | b);
  }

  dest[0] = r;
  dest[1] = g;
  dest[2] = b;
  dest[3] = a;
}

void fixRgb5A3(byte[] dest, byte[] src, int w, int h)
{
  //convert to rgba8 during block swapping
  //4x4 tiles
  int si = 0;
  for(int y = 0; y < h; y += 4)
    for(int x = 0; x < w; x += 4)
      for(int dy = 0; dy < 4; ++dy)
        for(int dx = 0; dx < 4; ++dx, si += 2)
          if(x + dx < w && y + dy < h)
          {
            ushort srcPixel = Helper.memWORD(src + si);
            rgb5a3ToRgba8(srcPixel, dest[4*(w*(y + dy) + x + dx)]);
          }
}

void s3tc1ReverseByte(byte b)
{
  byte b1 = (byte)(b & 0x3);
  byte b2 = (byte)(b & 0xc);
  byte b3 = (byte)(b & 0x30);
  byte b4 = (byte)(b & 0xc0);
  b = (byte)((b1 << 6) | (b2 << 2) | (b3 >> 2) | (b4 >> 6));
}

void fixS3TC1(byte[] dest, byte[] src, int w, int h)
{
  int s = 0;

  for(int y = 0; y < h/4; y += 2)
    for(int x = 0; x < w/4; x += 2)
      for(int dy = 0; dy < 2; ++dy)
        for(int dx = 0; dx < 2; ++dx, s += 8)
          if(4*(x + dx) < w && 4*(y + dy) < h)
            memcpy(dest[8*((y + dy)*w/4 + x + dx)], src[s], 8);

  //s3tc1 on the cube is a bit different from s3tc1 on pc graphic cards:
  for(int k = 0; k < w*h/2; k += 8)
  {
      byte tmp1 = dest[k];
      byte tmp2 = dest[k+1];
      dest[k] = tmp2;
      dest[k+1] = tmp1;
      tmp1 = dest[k+2];
      tmp2 = dest[k+3];
       dest[k+2] = tmp2;
      dest[k+3] = tmp1;

    s3tc1ReverseByte(dest[k+4]);
    s3tc1ReverseByte(dest[k+5]);
    s3tc1ReverseByte(dest[k+6]);
    s3tc1ReverseByte(dest[k+7]);
  }
}

int getUnpackedPixSize(byte paletteFormat)
{
  if(paletteFormat == PAL_A8_I8)
    return 2;
  return 4;
}

void unpackPixel(int index, byte[] dest, byte[] palette, byte paletteFormat)
{
  switch(paletteFormat)
  {
    case PAL_A8_I8: //a8i8 -> i8a8
      dest[0] = palette[2*index + 1];
      dest[1] = palette[2*index + 0];
      break;

    case PAL_R5_G6_B5: //r5g6b5 -> rgba8
      r5g6b5ToRgba8(Helper.memWORD(palette + 2*index), dest);
      break;
      
    case PAL_A3_RGB5: //rgb5a3 -> rgba8
      rgb5a3ToRgba8(Helper.memWORD(palette + 2*index), dest);
      break;
  }
}

void unpack8(byte[] dst, byte[] src, int w, int h,
             byte[] palette, byte paletteFormat)
{
  int pixSize = getUnpackedPixSize(paletteFormat);
  byte[] runner = dst;

  for(int y = 0; y < h; ++y)
    for(int x = 0; x < w; ++x, runner += pixSize)
      unpackPixel(src[y*w + x], runner, palette, paletteFormat);
}

void unpack16(byte[] dst, byte[] src, int w, int h,
              byte[] palette, byte paletteFormat)
{
  int pixSize = getUnpackedPixSize(paletteFormat);
  byte[] runner = dst;

  for(int y = 0; y < h; ++y)
    for(int x = 0; x < w; ++x, runner += pixSize)
    {
      //fix4x4() swaps words to little endian...
      ushort index = Helper.memWORD_le(src + 2*(y*w + x));
      unpackPixel(index & 0x3fff, runner, palette, paletteFormat);
    }
}
        //returns how many bytes an image of given format
        //and dimensions needs in memory after uncompression etc
        private static int getUncompressedBufferSize(byte format, int w, int h, byte paletteFormat)
        {
            int w4 = w + (4 - w % 4) % 4;
            int h4 = h + (4 - h % 4) % 4;

            switch (getUncompressedBufferFormat(format, paletteFormat))
            {
                case I8:
                    return w * h;
                case I8_A8:
                    return w * h * 2;
                case RGBA8:
                    return w * h * 4;
                case DXT1:
                    return w4 * h4 / 2;
                default:
                    return -1;
            }
        }

        private static byte getUncompressedBufferFormat(byte format, byte paletteFormat)
        {
            switch (format)
            {
                case I4:
                case I8:
                    return I8;
                case A4_I4: //a4i4 -> i8a8
                case A8_I8: //a8i8 -> i8a8
                    return I8_A8;
                case R5_G6_B5:
                case A3_RGB5:
                case ARGB8:
                    return RGBA8;

                case INDEX4:
                case INDEX8:
                case INDEX14_X2:
                    switch (paletteFormat)
                    {
                        case PAL_A8_I8: //a8i8 -> i8a8
                            return I8_A8;
                        case PAL_R5_G6_B5: //r5g6b5 -> rgba8
                        case PAL_A3_RGB5: //rgb5a3 -> rgba8
                            return RGBA8;
                        default:
                            return 0xFF;
                    }

                case S3TC1:
                    return DXT1;
                default:
                    return 0xFF;
            }

        }


    }
}
